using UnityEngine;
using UnityEngine.Rendering;
using System.Collections;

//-----------------------------------------------------------------------------
// Copyright 2012-2021 RenderHeads Ltd.  All rights reserved.
//-----------------------------------------------------------------------------

namespace RenderHeads.Media.AVProMovieCapture
{
	/// <summary>
	/// Capture from the screen (backbuffer).  Everything is captured as it appears on the screen, including IMGUI rendering.
	/// This component waits for the frame to be completely rendered and then captures it.
	/// </summary>
	[AddComponentMenu("AVPro Movie Capture/Capture From Screen", 0)]
	public class CaptureFromScreen : CaptureBase
	{
		//private const int NewFrameSleepTimeMs = 6;
		[SerializeField] bool _captureMouseCursor = false;
		[SerializeField] MouseCursor _mouseCursor = null;

		private System.IntPtr _targetNativePointer = System.IntPtr.Zero;
		private RenderTexture _resolveTexture = null;
		private CommandBuffer _commandBuffer = null;

		public bool CaptureMouseCursor
		{
			get { return _captureMouseCursor; }
			set { _captureMouseCursor = value; }
		}

		public MouseCursor MouseCursor
		{
			get { return _mouseCursor; }
			set { _mouseCursor = value; }
		}

		public override bool PrepareCapture()
		{
			if (_capturing)
			{
				return false;
			}
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
			if (SystemInfo.graphicsDeviceVersion.StartsWith("Direct3D 9"))
			{
				Debug.LogError("[AVProMovieCapture] Direct3D9 not yet supported, please use Direct3D11 instead.");
				return false;
			}
			else if (SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL") && !SystemInfo.graphicsDeviceVersion.Contains("emulated"))
			{
				Debug.LogError("[AVProMovieCapture] OpenGL not yet supported for CaptureFromScreen component, please use Direct3D11 instead. You may need to switch your build platform to Windows.");
				return false;
			}
#endif

			if (_mouseCursor != null)
			{
				_mouseCursor.enabled = _captureMouseCursor;
			}

#if UNITY_EDITOR
			if (Display.displays.Length > 1)
			{
				bool isSecondDisplayActive = false;
				for (int i = 1; i < Display.displays.Length; i++)
				{
					if (Display.displays[i].active)
					{
						isSecondDisplayActive = true;
						break;
					}
				}
				if (isSecondDisplayActive)
				{
					Debug.LogError("[AVProMovieCapture] CaptureFromScreen doesn't work correctly (can cause stretching or incorrect display capture) when there are multiple displays are active.  Use CaptureFromCamera instead.");
				}				
			}
#endif

			SelectRecordingResolution(Screen.width, Screen.height);

			_pixelFormat = NativePlugin.PixelFormat.RGBA32;
			if (SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL") && !SystemInfo.graphicsDeviceVersion.Contains("emulated"))
			{
				// TODO: add this back in once we have fixed opengl support
				_pixelFormat = NativePlugin.PixelFormat.BGRA32;
				_isTopDown = true;
			}
			else
			{
				_isTopDown = false;

				if (_isDirectX11)
				{
					_isTopDown = false;
				}
			}

			GenerateFilename();

			return base.PrepareCapture();
		}

		private void CopyRenderTargetToTexture()
		{
#if false
			// RJT TODO: If using D3D12 we need to read the current 'Display.main.colorBuffer', pass it down
			// to native and extract the texture using 'IUnityGraphicsD3D12v5::TextureFromRenderBuffer()'
			// - Although, as is, this doesn't work: https://forum.unity.com/threads/direct3d12-native-plugin-render-to-screen.733025/
			if (_targetNativePointer == System.IntPtr.Zero)
			{
				_targetNativePointer = Display.main.colorBuffer.GetNativeRenderBufferPtr();
//						_targetNativePointer = Graphics.activeColorBuffer.GetNativeRenderBufferPtr();
				NativePlugin.SetColourBuffer(_handle, _targetNativePointer);
			}
#endif
#if true
			if ((_targetNativePointer == System.IntPtr.Zero) ||
				(_resolveTexture && ((_resolveTexture.width != Screen.width) || (_resolveTexture.height != Screen.height)))
			)
			{
				FreeRenderResources();

				// Create RT matching screen extents
				_resolveTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1);
				_resolveTexture.Create();
				_targetNativePointer = _resolveTexture.GetNativeTexturePtr();
				NativePlugin.SetTexturePointer(_handle, _targetNativePointer);

				// Create command buffer
				_commandBuffer = new CommandBuffer();
				_commandBuffer.name = "AVPro Movie Capture copy";
				_commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, _resolveTexture);
			}
#endif

			Graphics.ExecuteCommandBuffer(_commandBuffer);
		}

		private void FreeRenderResources()
		{
			// Command buffer
			if (_commandBuffer != null)
			{
				_commandBuffer.Release();
				_commandBuffer = null;
			}

			// Resolve texture
			_targetNativePointer = System.IntPtr.Zero;
			if (_resolveTexture)
			{
				RenderTexture.ReleaseTemporary(_resolveTexture);
				_resolveTexture = null;
			}
		}

		public override void UnprepareCapture()
		{
			if (_handle != -1)
			{
				#if false
				NativePlugin.SetColourBuffer(_handle, System.IntPtr.Zero);
				#endif
				NativePlugin.SetTexturePointer(_handle, System.IntPtr.Zero);
			}

			FreeRenderResources();

			if (_mouseCursor != null)
			{
				_mouseCursor.enabled = false;
			}

			base.UnprepareCapture();
		}

		private IEnumerator FinalRenderCapture()
		{
			yield return _waitForEndOfFrame;

			TickFrameTimer();

			bool canGrab = true;

			if (IsUsingMotionBlur())
			{
				// If the motion blur is still accumulating, don't grab this frame
				canGrab = _motionBlur.IsFrameAccumulated;
			}

			if (canGrab && CanOutputFrame())
			{
				// Grab final RenderTexture into texture and encode
				EncodeUnityAudio();

				// RJT NOTE: Separate D3D12 path for now as it can't grab native RT
				if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12)
				{
					CopyRenderTargetToTexture();
					RenderThreadEvent(NativePlugin.PluginEvent.CaptureFrameBuffer);
				}
				else
				{
					RenderThreadEvent(NativePlugin.PluginEvent.CaptureFrameBuffer);

					// RJT NOTE: Causes screen flickering under D3D12, even if we're not doing any rendering at native level
					if (SystemInfo.graphicsDeviceType != GraphicsDeviceType.Direct3D12)
					{
						GL.InvalidateState();
					}
				}

				UpdateFPS();
			}

			RenormTimer();

			//yield return null;
		}

		public override void UpdateFrame()
		{
			if (_capturing && !_paused)
			{
				StartCoroutine(FinalRenderCapture());
			}
			base.UpdateFrame();
		}
	}
}